use std::{ffi::c_int, io, fmt};
use std::error::Error as StdError;

use crate::ipc;


#[derive(Copy, Clone, Debug, PartialEq)]
pub enum UnixError {
    Errno(c_int),
    ChannelClosed,
}

impl UnixError {
    pub(super) fn last() -> UnixError {
        UnixError::Errno(io::Error::last_os_error().raw_os_error().unwrap())
    }

    #[allow(dead_code)]
    pub fn channel_is_closed(&self) -> bool {
        *self == UnixError::ChannelClosed
    }
}

impl fmt::Display for UnixError {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            UnixError::Errno(errno) => fmt::Display::fmt(&io::Error::from_raw_os_error(errno), fmt),
            UnixError::ChannelClosed => write!(fmt, "All senders for this socket closed"),
        }
    }
}

impl StdError for UnixError {}

impl From<UnixError> for bincode::Error {
    fn from(unix_error: UnixError) -> Self {
        io::Error::from(unix_error).into()
    }
}

impl From<UnixError> for io::Error {
    fn from(unix_error: UnixError) -> io::Error {
        match unix_error {
            UnixError::Errno(errno) => io::Error::from_raw_os_error(errno),
            UnixError::ChannelClosed => io::Error::new(io::ErrorKind::ConnectionReset, unix_error),
        }
    }
}

impl From<UnixError> for ipc::IpcError {
    fn from(error: UnixError) -> Self {
        match error {
            UnixError::ChannelClosed => ipc::IpcError::Disconnected,
            e => ipc::IpcError::Io(io::Error::from(e)),
        }
    }
}

impl From<UnixError> for ipc::TryRecvError {
    fn from(error: UnixError) -> Self {
        match error {
            UnixError::ChannelClosed => ipc::TryRecvError::IpcError(ipc::IpcError::Disconnected),
            UnixError::Errno(code) if code == libc::EAGAIN || code == libc::EWOULDBLOCK => {
                ipc::TryRecvError::Empty
            },
            e => ipc::TryRecvError::IpcError(ipc::IpcError::Io(io::Error::from(e))),
        }
    }
}

impl From<io::Error> for UnixError {
    fn from(e: io::Error) -> UnixError {
        if let Some(errno) = e.raw_os_error() {
            UnixError::Errno(errno)
        } else {
            assert!(e.kind() == io::ErrorKind::ConnectionReset);
            UnixError::ChannelClosed
        }
    }
}